Micron Document
🎖️GitЯра🎖️

Node / meshtastic / Meshtastic-Android / files / androidApp / src / google / kotlin / org / meshtastic / app / map / offline / pmtiles / OfflineRegionExtractor.kt

Displaying Raw • Download

androidApp/src/google/kotlin/org/meshtastic/app/map/offline/pmtiles/OfflineRegionExtractor.kt renovate/fastlane-2.x-lockfile (056eed64) Text, 8.28 KB

T8b949e/*
* Copyright (c) 2026 Meshtastic LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
Tff7b72package T7ee787org.meshtastic.app.map.offline.pmtiles

Tff7b72import T7ee787ch.poole.geo.pmtiles.Constants
Tff7b72import T7ee787ch.poole.geo.pmtiles.HttpUrlConnectionChannel
Tff7b72import T7ee787ch.poole.geo.pmtiles.Reader
Tff7b72import T7ee787co.touchlab.kermit.Logger
Tff7b72import T7ee787com.google.android.gms.maps.model.LatLngBounds
Tff7b72import T7ee787kotlinx.coroutines.CancellationException
Tff7b72import T7ee787kotlinx.coroutines.flow.Flow
Tff7b72import T7ee787kotlinx.coroutines.flow.flow
Tff7b72import T7ee787kotlinx.coroutines.flow.flowOn
Tff7b72import T7ee787kotlinx.coroutines.sync.Mutex
Tff7b72import T7ee787kotlinx.coroutines.sync.withLock
Tff7b72import T7ee787org.meshtastic.core.common.util.ioDispatcher
Tff7b72import T7ee787java.io.IOException
Tff7b72import T7ee787java.util.zip.GZIPInputStream
Tff7b72import T7ee787kotlin.uuid.Uuid

T8b949e/** Public alongside [OfflineRegion] — see that class's doc comment for why these two aren't `internal`. */
Tff7b72sealed Tff7b72interface T56d364OfflineDownloadState Tb4b4b4{
Tff7b72data Tff7b72class T56d364InProgressTb4b4b4(Tff7b72val Te6edf3completedTb4b4b4: Tffa657IntTb4b4b4, Tff7b72val Te6edf3totalTb4b4b4: Tffa657IntTb4b4b4) Tb4b4b4: Te6edf3OfflineDownloadState

Tff7b72data Tff7b72class T56d364CompleteTb4b4b4(Tff7b72val Te6edf3regionTb4b4b4: Te6edf3OfflineRegionTb4b4b4) Tb4b4b4: Te6edf3OfflineDownloadState

Tff7b72data Tff7b72class T56d364FailedTb4b4b4(Tff7b72val Te6edf3reasonTb4b4b4: Te6edf3OfflineDownloadFailureTb4b4b4) Tb4b4b4: Te6edf3OfflineDownloadState
Tb4b4b4}

Tff7b72enum Tff7b72class T56d364OfflineDownloadFailure Tb4b4b4{
T8b949e/** None of the last [PmTilesDailyBuild]-searched days had a published build. */
Te6edf3NO_BUILD_AVAILABLETb4b4b4,

T8b949e/** More tiles than [OfflineRegionExtractor.MAX_TILES_PER_REGION] — likely too large an area or too deep a zoom. */
Te6edf3TILE_LIMIT_EXCEEDEDTb4b4b4,

T8b949e/** Already at [OfflineRegionExtractor.MAX_REGIONS] downloaded regions. */
Te6edf3REGION_LIMIT_EXCEEDEDTb4b4b4,

T8b949e/** Existing regions already occupy [OfflineRegionExtractor.MAX_TOTAL_BYTES]. */
Te6edf3STORAGE_LIMIT_EXCEEDEDTb4b4b4,

T8b949e/** A network or disk failure partway through; nothing partial is kept. */
Te6edf3IO_ERRORTb4b4b4,
Tb4b4b4}

T8b949e/**
* Extracts an offline vector region: enumerates the tiles [bounds]/[zoomRange] cover, fetches each individually from
* the current Protomaps daily build via [ch.poole.geo.pmtiles.Reader] (which range-requests only that tile's own bytes,
* not the surrounding multi-gigabyte file), and writes them into a local `.mbtiles` archive.
*
* Deliberately simpler than the sibling iOS app's extractor, which coalesces adjacent tiles' byte ranges into batched
* HTTP requests before downloading anything (`PMTilesExtractor.swift`) — this fetches one tile per request. That is the
* reason [MAX_TILES_PER_REGION] here (2,000) is far below iOS's 600,000: at one round trip per tile, anything near that
* many tiles would take an impractically long time on a phone connection. Coalescing requests to raise this cap is the
* natural next step; see the module README.
*/
Tff7b72internal Tff7b72class T56d364OfflineRegionExtractorTb4b4b4(Tff7b72private Tff7b72val Te6edf3storeTb4b4b4: Te6edf3OfflineRegionStoreTb4b4b4) Tb4b4b4{

T8b949e// Serializes downloads so two concurrent starts can't both pass the same totalBytes()/list() precheck and
T8b949e// together blow past MAX_TOTAL_BYTES/MAX_REGIONS — every download must go through this one instance.
Tff7b72private Tff7b72val Te6edf3downloadMutex Tff7b72= Te6edf3MutexTb4b4b4(Tb4b4b4)

Tff7b72fun Td2a8ffdownloadTb4b4b4(Te6edf3boundsTb4b4b4: Te6edf3LatLngBoundsTb4b4b4, Te6edf3zoomRangeTb4b4b4: Te6edf3IntRangeTb4b4b4)Tb4b4b4: Te6edf3FlowTff7b72<Te6edf3OfflineDownloadStateTff7b72> Tff7b72= Te6edf3flow Tb4b4b4{
Te6edf3downloadMutexTb4b4b4.Te6edf3withLock Tb4b4b4{
Tff7b72val Te6edf3tiles Tff7b72= Te6edf3OfflineRegionTileSetTb4b4b4.Te6edf3tilesTb4b4b4(Te6edf3boundsTb4b4b4, Te6edf3zoomRangeTb4b4b4)
Tff7b72when Tb4b4b4{
Te6edf3tilesTb4b4b4.Te6edf3size Tff7b72> Te6edf3MAX_TILES_PER_REGION Tff7b72-Tff7b72> Tb4b4b4{
Te6edf3emitTb4b4b4(Te6edf3OfflineDownloadStateTb4b4b4.Te6edf3FailedTb4b4b4(Te6edf3OfflineDownloadFailureTb4b4b4.Te6edf3TILE_LIMIT_EXCEEDEDTb4b4b4)Tb4b4b4)
Tff7b72returnTf0883e@withLock
Tb4b4b4}

Te6edf3storeTb4b4b4.Te6edf3listTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3size Tff7b72>Tff7b72= Te6edf3MAX_REGIONS Tff7b72-Tff7b72> Tb4b4b4{
Te6edf3emitTb4b4b4(Te6edf3OfflineDownloadStateTb4b4b4.Te6edf3FailedTb4b4b4(Te6edf3OfflineDownloadFailureTb4b4b4.Te6edf3REGION_LIMIT_EXCEEDEDTb4b4b4)Tb4b4b4)
Tff7b72returnTf0883e@withLock
Tb4b4b4}

Te6edf3storeTb4b4b4.Te6edf3totalBytesTb4b4b4(Tb4b4b4) Tff7b72>Tff7b72= Te6edf3MAX_TOTAL_BYTES Tff7b72-Tff7b72> Tb4b4b4{
Te6edf3emitTb4b4b4(Te6edf3OfflineDownloadStateTb4b4b4.Te6edf3FailedTb4b4b4(Te6edf3OfflineDownloadFailureTb4b4b4.Te6edf3STORAGE_LIMIT_EXCEEDEDTb4b4b4)Tb4b4b4)
Tff7b72returnTf0883e@withLock
Tb4b4b4}
Tb4b4b4}

Tff7b72val Te6edf3url Tff7b72= Te6edf3PmTilesDailyBuildTb4b4b4.Te6edf3resolveLatestUrlTb4b4b4(Tb4b4b4)
Tff7b72if Tb4b4b4(Te6edf3url Tff7b72=Tff7b72= Tff7b72nullTb4b4b4) Tb4b4b4{
Te6edf3emitTb4b4b4(Te6edf3OfflineDownloadStateTb4b4b4.Te6edf3FailedTb4b4b4(Te6edf3OfflineDownloadFailureTb4b4b4.Te6edf3NO_BUILD_AVAILABLETb4b4b4)Tb4b4b4)
Tff7b72returnTf0883e@withLock
Tb4b4b4}

Tff7b72val Te6edf3id Tff7b72= Te6edf3UuidTb4b4b4.Te6edf3randomTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3toStringTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3archiveFile Tff7b72= Te6edf3storeTb4b4b4.Te6edf3archiveFileTb4b4b4(Te6edf3idTb4b4b4)

T8b949e// store.add stays inside the guard: the manifest write can throw too, and an archive with no
T8b949e// manifest entry is an orphan nothing would ever delete.
Tff7b72val Te6edf3region Tff7b72=
Tff7b72try Tb4b4b4{
Te6edf3extractIntoTb4b4b4(Te6edf3archiveFileTb4b4b4, Te6edf3urlTb4b4b4, Te6edf3tilesTb4b4b4) Tb4b4b4{ Te6edf3completed Tff7b72-Tff7b72>
Te6edf3emitTb4b4b4(Te6edf3OfflineDownloadStateTb4b4b4.Te6edf3InProgressTb4b4b4(Te6edf3completedTb4b4b4, Te6edf3tilesTb4b4b4.Te6edf3sizeTb4b4b4)Tb4b4b4)
Tb4b4b4}
Te6edf3OfflineRegionTb4b4b4(
Te6edf3id Tff7b72= Te6edf3idTb4b4b4,
Te6edf3southLat Tff7b72= Te6edf3boundsTb4b4b4.Te6edf3southwestTb4b4b4.Te6edf3latitudeTb4b4b4,
Te6edf3westLon Tff7b72= Te6edf3boundsTb4b4b4.Te6edf3southwestTb4b4b4.Te6edf3longitudeTb4b4b4,
Te6edf3northLat Tff7b72= Te6edf3boundsTb4b4b4.Te6edf3northeastTb4b4b4.Te6edf3latitudeTb4b4b4,
Te6edf3eastLon Tff7b72= Te6edf3boundsTb4b4b4.Te6edf3northeastTb4b4b4.Te6edf3longitudeTb4b4b4,
Te6edf3minZoom Tff7b72= Te6edf3zoomRangeTb4b4b4.Te6edf3firstTb4b4b4,
Te6edf3maxZoom Tff7b72= Te6edf3zoomRangeTb4b4b4.Te6edf3lastTb4b4b4,
Te6edf3tileCount Tff7b72= Te6edf3tilesTb4b4b4.Te6edf3sizeTb4b4b4.Te6edf3toLongTb4b4b4(Tb4b4b4)Tb4b4b4,
Te6edf3byteSize Tff7b72= Te6edf3archiveFileTb4b4b4.Te6edf3lengthTb4b4b4(Tb4b4b4)Tb4b4b4,
Te6edf3createdAtEpochSeconds Tff7b72= Te6edf3SystemTb4b4b4.Te6edf3currentTimeMillisTb4b4b4(Tb4b4b4) Tff7b72/ Te6edf3MILLIS_PER_SECONDTb4b4b4,
Tb4b4b4)
Tb4b4b4.Te6edf3also Tb4b4b4{ Te6edf3storeTb4b4b4.Te6edf3addTb4b4b4(Tffa657itTb4b4b4) Tb4b4b4}
Tb4b4b4} Tff7b72catch Tb4b4b4(Te6edf3eTb4b4b4: Te6edf3IOExceptionTb4b4b4) Tb4b4b4{
Te6edf3LOGTb4b4b4.Te6edf3wTb4b4b4(Te6edf3eTb4b4b4) Tb4b4b4{ Ta5d6ff"Ta5d6ffOffline region extraction failedTa5d6ff" Tb4b4b4}
Te6edf3archiveFileTb4b4b4.Te6edf3deleteTb4b4b4(Tb4b4b4)
Te6edf3emitTb4b4b4(Te6edf3OfflineDownloadStateTb4b4b4.Te6edf3FailedTb4b4b4(Te6edf3OfflineDownloadFailureTb4b4b4.Te6edf3IO_ERRORTb4b4b4)Tb4b4b4)
Tff7b72returnTf0883e@withLock
Tb4b4b4} Tff7b72catch Tb4b4b4(Te6edf3eTb4b4b4: Te6edf3CancellationExceptionTb4b4b4) Tb4b4b4{
T8b949e// Not an IOException, so it needs its own cleanup — a cancelled download must not leave its
T8b949e// partial archive file behind, and cancellation must still propagate, never be swallowed.
Te6edf3archiveFileTb4b4b4.Te6edf3deleteTb4b4b4(Tb4b4b4)
Tff7b72throw Te6edf3e
Tb4b4b4}
Te6edf3emitTb4b4b4(Te6edf3OfflineDownloadStateTb4b4b4.Te6edf3CompleteTb4b4b4(Te6edf3regionTb4b4b4)Tb4b4b4)
Tb4b4b4}
Tb4b4b4}
Tb4b4b4.Te6edf3flowOnTb4b4b4(Te6edf3ioDispatcherTb4b4b4)

Tff7b72private Tff7b72suspend Tff7b72fun Td2a8ffextractIntoTb4b4b4(
Te6edf3archiveFileTb4b4b4: Te6edf3javaTb4b4b4.Te6edf3ioTb4b4b4.Te6edf3FileTb4b4b4,
Te6edf3urlTb4b4b4: Te6edf3javaTb4b4b4.Te6edf3netTb4b4b4.Te6edf3URLTb4b4b4,
Te6edf3tilesTb4b4b4: Te6edf3ListTff7b72<Te6edf3TileIndexTff7b72>Tb4b4b4,
Te6edf3onProgressTb4b4b4: Te6edf3suspend Tb4b4b4(Te6edf3completedTb4b4b4: Tffa657IntTb4b4b4) Tff7b72-Tff7b72> Tffa657UnitTb4b4b4,
Tb4b4b4) Tb4b4b4{
Te6edf3ReaderTb4b4b4(Te6edf3HttpUrlConnectionChannelTb4b4b4(Te6edf3urlTb4b4b4)Tb4b4b4)Tb4b4b4.Te6edf3use Tb4b4b4{ Te6edf3reader Tff7b72-Tff7b72>
Te6edf3OfflineVectorArchiveTb4b4b4.Te6edf3createTb4b4b4(Te6edf3archiveFileTb4b4b4, Te6edf3attribution Tff7b72= Te6edf3ATTRIBUTIONTb4b4b4)Tb4b4b4.Te6edf3use Tb4b4b4{ Te6edf3archive Tff7b72-Tff7b72>
Te6edf3tilesTb4b4b4.Te6edf3forEachIndexed Tb4b4b4{ Te6edf3indexTb4b4b4, Te6edf3tile Tff7b72-Tff7b72>
Tff7b72val Te6edf3raw Tff7b72= Te6edf3readerTb4b4b4.Te6edf3getTileTb4b4b4(Te6edf3tileTb4b4b4.Te6edf3zoomTb4b4b4, Te6edf3tileTb4b4b4.Te6edf3xTb4b4b4, Te6edf3tileTb4b4b4.Te6edf3yTb4b4b4)
Tff7b72if Tb4b4b4(Te6edf3raw Tff7b72!Tff7b72= Tff7b72nullTb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3bytes Tff7b72= Tff7b72if Tb4b4b4(Te6edf3readerTb4b4b4.Te6edf3tileCompression Tff7b72=Tff7b72= Te6edf3ConstantsTb4b4b4.Te6edf3COMPRESSION_GZIPTb4b4b4) Te6edf3gunzipTb4b4b4(Te6edf3rawTb4b4b4) Tff7b72else Te6edf3raw
Te6edf3archiveTb4b4b4.Te6edf3writeTileTb4b4b4(Te6edf3tileTb4b4b4.Te6edf3zoomTb4b4b4, Te6edf3tileTb4b4b4.Te6edf3xTb4b4b4, Te6edf3tileTb4b4b4.Te6edf3yTb4b4b4, Te6edf3bytesTb4b4b4)
Tb4b4b4}
Tff7b72val Te6edf3completed Tff7b72= Te6edf3index Tff7b72+ T79c0ff1
Tff7b72if Tb4b4b4(Te6edf3completed Tff7b72% Te6edf3PROGRESS_STRIDE Tff7b72=Tff7b72= T79c0ff0 Tff7b72|Tff7b72| Te6edf3completed Tff7b72=Tff7b72= Te6edf3tilesTb4b4b4.Te6edf3sizeTb4b4b4) Te6edf3onProgressTb4b4b4(Te6edf3completedTb4b4b4)
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}

Tff7b72private Tff7b72fun Td2a8ffgunzipTb4b4b4(Te6edf3bytesTb4b4b4: Te6edf3ByteArrayTb4b4b4)Tb4b4b4: Te6edf3ByteArray Tff7b72= Te6edf3GZIPInputStreamTb4b4b4(Te6edf3bytesTb4b4b4.Te6edf3inputStreamTb4b4b4(Tb4b4b4)Tb4b4b4)Tb4b4b4.Te6edf3use Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3readBytesTb4b4b4(Tb4b4b4) Tb4b4b4}

Tff7b72internal Tff7b72companion Tff7b72object Tb4b4b4{
Tff7b72private Tff7b72val Te6edf3LOG Tff7b72= Te6edf3LoggerTb4b4b4.Te6edf3withTagTb4b4b4(Ta5d6ff"Ta5d6ffOfflineRegionExtractorTa5d6ff"Tb4b4b4)

T8b949e/** See the class doc: one HTTP round trip per tile makes iOS's 600,000-tile cap impractical here. */
Tff7b72const Tff7b72val Te6edf3MAX_TILES_PER_REGION Tff7b72= T79c0ff2Te6edf3_000
Tff7b72const Tff7b72val Te6edf3MAX_REGIONS Tff7b72= T79c0ff1T79c0ff0
Tff7b72const Tff7b72val Te6edf3MAX_TOTAL_BYTES Tff7b72= T79c0ff3T79c0ff0T79c0ff0L Tff7b72* T79c0ff1T79c0ff0T79c0ff2T79c0ff4 Tff7b72* T79c0ff1T79c0ff0T79c0ff2T79c0ff4
Tff7b72private Tff7b72const Tff7b72val Te6edf3PROGRESS_STRIDE Tff7b72= T79c0ff1T79c0ff0
Tff7b72private Tff7b72const Tff7b72val Te6edf3MILLIS_PER_SECOND Tff7b72= T79c0ff1Te6edf3_000L

T8b949e/** Both the Protomaps build and the MVT layers it packages (OpenStreetMap) require attribution. */
Tff7b72const Tff7b72val Te6edf3ATTRIBUTION Tff7b72= Ta5d6ff"Ta5d6ff© OpenStreetMap contributors, © ProtomapsTa5d6ff"
Tb4b4b4}
Tb4b4b4}

Served by rngit 1.5.2 - Generated in 0.05s